昨天我們成功建立了測試環境並寫下第一個測試,今天要深入了解測試的核心 —「斷言(Assertions)」。
想像一下,你正在開發一個使用者註冊功能。產品經理說:「我們需要驗證使用者輸入的 email 格式、密碼強度、年齡範圍...」你心想:「這麼多驗證規則,怎麼確保每一個都正確運作?」
答案就是斷言!斷言是測試的核心,它告訴我們「期望」和「實際」結果是否相符。今天我們要學會使用各種斷言方法,讓測試更精準、更具表達力。
今天結束後,你將學會:
第一階段:打好基礎(Day 1-10)
├── Day 01 - 環境設置與第一個測試
├── Day 02 - 認識斷言(Assertions) ★ 今天在這裡
├── ...
└── (更多精彩內容待續)
斷言就像品質檢驗員,負責檢查產品是否符合規格。在測試中,斷言:
昨天我們已經使用了最基本的 toBe
斷言:
建立 tests/day02/basic-assertions.test.ts
:
import { describe, it, expect } from 'vitest'
describe('basic assertions', () => {
it('comparesEquality', () => {
const result = 2 + 3
expect(result).toBe(5)
})
it('differencesBetweenToBeAndToEqual', () => {
const obj1 = { name: 'Alice' }
const obj2 = { name: 'Alice' }
// toBe 檢查是否為同一個物件(參考相等)
expect(obj1).not.toBe(obj2)
// toEqual 檢查內容是否相同(值相等)
expect(obj1).toEqual(obj2)
})
})
更新 tests/day02/basic-assertions.test.ts
:
import { describe, it, expect } from 'vitest'
describe('common assertion methods', () => {
it('strictEquality', () => {
expect(5).toBe(5)
expect('hello').toBe('hello')
})
it('deepComparison', () => {
const user = { name: 'Bob', age: 30 }
expect(user).toEqual({ name: 'Bob', age: 30 })
})
it('checksNullAndUndefined', () => {
const nullValue = null
const undefinedValue = undefined
expect(nullValue).toBeNull()
expect(undefinedValue).toBeUndefined()
})
it('truthyAndFalsyChecks', () => {
expect(true).toBeTruthy()
expect('').toBeFalsy()
expect(0).toBeFalsy()
expect(1).toBeTruthy()
})
it('checksDataTypes', () => {
expect(42).toBeTypeOf('number')
expect('hello').toBeTypeOf('string')
})
})
數值比較在驗證功能中經常用到:
建立 tests/day02/number-assertions.test.ts
:
import { describe, it, expect } from 'vitest'
describe('number comparison assertions', () => {
it('greaterAndLessThan', () => {
expect(10).toBeGreaterThan(5)
expect(5).toBeLessThan(10)
})
it('greaterAndLessOrEqual', () => {
expect(10).toBeGreaterThanOrEqual(10)
expect(5).toBeLessThanOrEqual(5)
})
it('floatApproximation', () => {
expect(0.1 + 0.2).toBeCloseTo(0.3)
})
})
字串驗證在表單處理中非常重要:
建立 tests/day02/string-assertions.test.ts
:
import { describe, it, expect } from 'vitest'
describe('string related assertions', () => {
it('containsSubstring', () => {
expect('Hello World').toContain('World')
expect('user@example.com').toContain('@')
})
it('regexMatching', () => {
expect('hello123').toMatch(/^hello\d+$/)
expect('test@email.com').toMatch(/\w+@\w+\.\w+/)
})
it('checksLength', () => {
expect('hello').toHaveLength(5)
expect('').toHaveLength(0)
})
})
處理複雜資料結構時需要更強大的斷言:
建立 tests/day02/collection-assertions.test.ts
:
import { describe, it, expect } from 'vitest'
describe('collection assertions', () => {
it('arrayContains', () => {
const fruits = ['apple', 'banana', 'orange']
expect(fruits).toContain('banana')
expect(fruits).toHaveLength(3)
})
it('objectProperties', () => {
const user = {
id: 1,
name: 'Alice',
email: 'alice@example.com'
}
expect(user).toHaveProperty('name')
expect(user).toHaveProperty('email', 'alice@example.com')
expect(user).not.toHaveProperty('password')
})
it('objectMatching', () => {
const product = {
id: 1,
name: 'iPhone',
price: 999
}
// 部分匹配
expect(product).toMatchObject({
name: 'iPhone',
price: 999
})
})
})
讓我們實作一個驗證器模組來練習各種斷言:
建立 tests/day02/validator.test.ts
:
import { describe, it, expect } from 'vitest'
import { isValidEmail, isStrongPassword, isValidAge } from '../../src/validator'
describe('validator function tests', () => {
it('validatesEmail', () => {
expect(isValidEmail('user@example.com')).toBe(true)
expect(isValidEmail('invalid')).toBe(false)
expect(isValidEmail('@example.com')).toBe(false)
})
it('validatesStrongPassword', () => {
expect(isStrongPassword('MyP@ss123')).toBe(true)
expect(isStrongPassword('weak')).toBe(false)
expect(isStrongPassword('NoNumbers!')).toBe(false)
})
it('validatesAge', () => {
expect(isValidAge(25)).toBe(true)
expect(isValidAge(17)).toBe(false)
expect(isValidAge(121)).toBe(false)
})
})
現在讓我們實作對應的驗證函數,這是一個非常實用的練習:
建立 src/validator.ts
:
export function isValidEmail(email: string): boolean {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
return emailRegex.test(email)
}
export function isStrongPassword(password: string): boolean {
if (password.length < 8) return false
const hasUpperCase = /[A-Z]/.test(password)
const hasLowerCase = /[a-z]/.test(password)
const hasNumbers = /\d/.test(password)
const hasSpecialChar = /[!@#$%^&*(),.?":{}|<>]/.test(password)
return hasUpperCase && hasLowerCase && hasNumbers && hasSpecialChar
}
export function isValidAge(age: number): boolean {
return age >= 18 && age <= 120
}
執行所有 Day 02 的測試:
npm test tests/day02
你應該會看到所有測試通過,並且能清楚了解每個斷言的作用。
試試看完成這些挑戰:
isValidEmail
函數增加更嚴格的驗證規則isValidPhoneNumber
函數並寫測試toBe()
而不是 toEqual()
?toBe()
: 嚴格相等比較toEqual()
: 深度內容比較toBeTruthy()
/ toBeFalsy()
: 真假值檢查toBeGreaterThan()
/ toBeLessThan()
: 數值比較toContain()
/ toMatch()
: 字串檢查今天我們深入學習了斷言的使用,從基本的相等比較到複雜的字串匹配、數值比較、陣列物件檢查。透過實作驗證器函數,我們練習了:
斷言是測試的基石,掌握好斷言的使用,你的測試就會更加準確和有說服力。記住:好的測試不只是驗證功能正確,更是幫助我們快速定位問題的利器。
明天我們將進入 TDD 的核心 —「紅綠重構循環」! 💪